package com.ikas.ai.utils;

import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.zip.Zip64Mode;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.DigestUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * File Util
 * <ul>
 * Read or write file
 * <li>{@link #readFile(String)} read file</li>
 * <li>{@link #writeFile(String, String, boolean)} write file from String</li>
 * <li>{@link #writeFile(String, String)} write file from String</li>
 * <li>{@link #writeFile(String, List, boolean)} write file from String List</li>
 * <li>{@link #writeFile(String, List)} write file from String List</li>
 * <li>{@link #writeFile(String, InputStream)} write file</li>
 * <li>{@link #writeFile(String, InputStream, boolean)} write file</li>
 * <li>{@link #writeFile(File, InputStream)} write file</li>
 * <li>{@link #writeFile(File, InputStream, boolean)} write file</li>
 * </ul>
 * <ul>
 * Operate file
 * <li>{@link #copyFile(String, String)}</li>
 * <li>{@link #getFileExtension(String)}</li>
 * <li>{@link #getFileName(String)}</li>
 * <li>{@link #getFileNameWithoutExtension(String)}</li>
 * <li>{@link #getFileSize(String)}</li>
 * <li>{@link #deleteFile(String)}</li>
 * <li>{@link #isFileExist(String)}</li>
 * <li>{@link #isFolderExist(String)}</li>
 * <li>{@link #makeFolders(String)}</li>
 * <li>{@link #makeDirs(String)}</li>
 * </ul>
 *
 * @author 黄旭辉
 */
@SuppressWarnings("all")
public class FileUtil extends cn.hutool.core.io.FileUtil {

    public static final String FILE_EXTENSION_SEPARATOR = ".";
    private static final String ENCODING = "UTF-8";
    private static Logger logger = LoggerFactory.getLogger(FileUtil.class);

    private FileUtil() {

    }

    /**
     * MultipartFile 转File
     *
     * @param file
     * @return
     */
    public static File multi2File(MultipartFile file) {
        File toFile = null;
        try {
            if (file.equals("") || file == null || file.getSize() <= 0) {
                throw new RuntimeException("文件不存在或损坏");
            } else {
                InputStream ins = file.getInputStream();
                toFile = new File(file.getOriginalFilename());
                inputStreamToFile(ins, toFile);
                ins.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return toFile;
    }

    /**
     * InputStream 转 File
     *
     * @param ins
     * @param file
     */
    public static void inputStreamToFile(InputStream ins, File file) {
        try {
            OutputStream os = new FileOutputStream(file);
            int bytesRead = 0;
            byte[] buffer = new byte[4096];
            while ((bytesRead = ins.read(buffer, 0, 4096)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            os.close();
            ins.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 查找指定目录下指定后缀名的文件,无递归查询
     *
     * @param dirPath
     * @return
     */
    public static List<String> getFiles(String dirPath) {
        List<String> fileNames = new ArrayList<>();
        File directory = new File(dirPath);
        if (!directory.exists() || directory.isFile()) {
            return fileNames;
        } else {
            File[] children = directory.listFiles();
            for (File child : children) {
                fileNames.add(child.getAbsolutePath());
            }
            return fileNames;
        }
    }

    /**
     * 将源文件复制到目标文件
     *
     * @param srcFile  源文件的完整路径
     * @param destFile 目标文件的完整路径
     */
    public static void copyFile(File srcFile, File destFile) throws IOException {
        File outputFolder = destFile.getParentFile();
        if (!outputFolder.exists()) {
            outputFolder.mkdirs();
        }
        try (FileInputStream in = new FileInputStream(srcFile);
             FileOutputStream out = new FileOutputStream(destFile);
             BufferedInputStream bufferIn = new BufferedInputStream(in);
             BufferedOutputStream bufferOut = new BufferedOutputStream(out)) {
            byte[] b = new byte[1024];
            int i = 0;
            while ((i = bufferIn.read(b)) != -1) {
                bufferOut.write(b, 0, i);
            }
            bufferOut.flush();
            out.flush();
        }
    }

    /**
     * 拷贝srcPath目录下所有文件及子目录下的文件
     *
     * @param srcPath
     * @param destPath
     */
    public static void copyFiles(String srcPath, String destPath) {
        File srcFile = new File(srcPath);
        if (!srcFile.exists()) {
            return;
        }

        try {
            File destFile = new File(destPath);
            if (srcFile.isDirectory()) {
                if (!destFile.exists()) {
                    destFile.mkdirs();
                }
                for (File tmpFile : srcFile.listFiles()) {
                    if (tmpFile.isDirectory()) {
                        copyFiles(tmpFile.getAbsolutePath(), destPath + "/" + tmpFile.getName());
                    } else {
                        copyFile(tmpFile, new File(destPath, tmpFile.getName()));
                    }
                }
            } else {
                copyFile(srcFile, destFile);
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }

    /**
     * 剪切源文件到目标文件
     *
     * @param srcFile
     * @param destFile
     * @throws IOException
     */
    public static void cutFile(String srcFile, String destFile) throws IOException {
        copyFile(new File(srcFile), new File(destFile));
        Files.delete(Paths.get(srcFile));
    }

    /**
     * 得到文件的字节数组
     *
     * @param filePath
     * @return
     * @throws IOException
     */
    public static byte[] getFileByteArray(String filePath) throws IOException {
        if (!new File(filePath).exists()) {
            logger.error("The file is not exist in specified file path! " + filePath);
            return new byte[0];
        }
        if (new File(filePath).isDirectory()) {
            logger.error("The directory path is specified!" + filePath);
            return new byte[0];
        }
        byte[] result;
        try (ByteArrayOutputStream out = new ByteArrayOutputStream();
             InputStream in = new FileInputStream(filePath)) {
            byte[] b = new byte[1024];
            int i = 0;
            while ((i = in.read(b)) != -1) {
                out.write(b, 0, i);
            }
            result = out.toByteArray();
        }
        return result;
    }

    /**
     * 将字节数组写入文件
     *
     * @param buffer
     * @param filePath
     * @param append
     * @throws IOException
     */
    public static void writeByteArrayToFile(byte[] buffer, String filePath, boolean append) throws IOException {
        File file = new File(filePath);
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        try (OutputStream out = new FileOutputStream(file, append);
             BufferedOutputStream bufferOut = new BufferedOutputStream(out);) {
            bufferOut.write(buffer);
            bufferOut.flush();
        }
    }

    /**
     * 读取文件内容
     *
     * @param fileName
     * @return
     */
    public static String readFile(String fileName) {
        StringBuilder sb = new StringBuilder();
        File file = new File(fileName);
        if (file.exists()) {
            try (FileInputStream input = new FileInputStream(file);
                 InputStreamReader read = new InputStreamReader(input, ENCODING);
                 BufferedReader buf = new BufferedReader(read)) {
                String lineTxt;
                while ((lineTxt = buf.readLine()) != null) {
                    sb.append(lineTxt + "\r\n");
                }
            } catch (Exception ex) {
                logger.error(ex.getMessage(), ex);
            }
        }
        return sb.toString().trim();
    }

    /**
     * read file
     *
     * @param filePath
     * @param charsetName The name of a supported {@link java.nio.charset.Charset <code>charset</code>}
     * @return if file not exist, return null, else return content of file
     * @throws RuntimeException if an error occurs while operator BufferedReader
     */
    public static StringBuilder readFile(String filePath, String charsetName) {
        File file = new File(filePath);
        StringBuilder fileContent = new StringBuilder();
        if (!file.isFile()) {
            return null;
        }
        try (InputStreamReader is = new InputStreamReader(new FileInputStream(file), charsetName);
             BufferedReader reader = new BufferedReader(is)) {
            String line = null;
            while ((line = reader.readLine()) != null) {
                if (!fileContent.toString().equals("")) {
                    fileContent.append("\r\n");
                }
                fileContent.append(line);
            }
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
        return fileContent;
    }

    /**
     * write file
     *
     * @param filePath
     * @param content
     * @param append   is append, if true, write to the end of file, else clear content of file and write into it
     * @return return false if content is empty, true otherwise
     * @throws RuntimeException if an error occurs while operator FileWriter
     */
    public static boolean writeFile(String filePath, String content, boolean append) {
        if (StringUtils.isEmpty(content)) {
            return false;
        }
        File file = new File(filePath);
        File dir = new File(file.getParent());
        if (!dir.exists()) {
            dir.mkdirs();
        }
        try (FileOutputStream fs = new FileOutputStream(filePath, append);
             OutputStreamWriter out = new OutputStreamWriter(fs, ENCODING)) {
            out.write(content);
            out.flush();
            fs.flush();
            out.close();
            fs.close();
            return true;
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
        return false;
    }

    /**
     * write file
     *
     * @param filePath
     * @param contentList
     * @param append      is append, if true, write to the end of file, else clear content of file and write into it
     * @return return false if contentList is empty, true otherwise
     * @throws RuntimeException if an error occurs while operator FileWriter
     */
    public static boolean writeFile(String filePath, List<String> contentList, boolean append) {
        if (ListUtil.isEmpty(contentList)) {
            return false;
        }
        makeDirs(filePath);
        try (FileWriter fileWriter = new FileWriter(filePath, append)) {
            int i = 0;
            for (String line : contentList) {
                if (i++ > 0) {
                    fileWriter.write("\r\n");
                }
                fileWriter.write(line);
            }
            return true;
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
        return false;
    }

    /**
     * write file, the string will be written to the begin of the file
     *
     * @param filePath
     * @param content
     * @return
     */
    public static boolean writeFile(String filePath, String content) {
        return writeFile(filePath, content, false);
    }

    /**
     * write file, the string list will be written to the begin of the file
     *
     * @param filePath
     * @param contentList
     * @return
     */
    public static boolean writeFile(String filePath, List<String> contentList) {
        return writeFile(filePath, contentList, false);
    }

    /**
     * write file, the bytes will be written to the begin of the file
     *
     * @param filePath
     * @param stream
     * @return
     * @see {@link #writeFile(String, InputStream, boolean)}
     */
    public static boolean writeFile(String filePath, InputStream stream) {
        return writeFile(filePath, stream, false);
    }

    /**
     * write file
     *
     * @param filePath the file to be opened for writing.
     * @param stream   the input stream
     * @param append   if <code>true</code>, then bytes will be written to the end of the file rather than the beginning
     * @return return true
     * @throws RuntimeException if an error occurs while operator FileOutputStream
     */
    public static boolean writeFile(String filePath, InputStream stream, boolean append) {
        return writeFile(new File(filePath), stream, append);
    }

    /**
     * write file, the bytes will be written to the begin of the file
     *
     * @param file
     * @param stream
     * @return
     * @see {@link #writeFile(File, InputStream, boolean)}
     */
    public static boolean writeFile(File file, InputStream stream) {
        return writeFile(file, stream, false);
    }

    /**
     * write file
     *
     * @param file   the file to be opened for writing.
     * @param stream the input stream
     * @param append if <code>true</code>, then bytes will be written to the end of the file rather than the beginning
     * @return return true
     * @throws RuntimeException if an error occurs while operator FileOutputStream
     */
    public static boolean writeFile(File file, InputStream stream, boolean append) {
        makeDirs(file.getAbsolutePath());
        try (OutputStream o = new FileOutputStream(file, append)) {
            byte[] data = new byte[1024];
            int length = -1;
            while ((length = stream.read(data)) != -1) {
                o.write(data, 0, length);
            }
            o.flush();
            return true;
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
        return false;
    }

    /**
     * copy file
     *
     * @param sourceFilePath
     * @param destFilePath
     * @return
     * @throws RuntimeException if an error occurs while operator FileOutputStream
     */
    public static boolean copyFile(String sourceFilePath, String destFilePath) {
        InputStream inputStream = null;
        try {
            inputStream = new FileInputStream(sourceFilePath);
        } catch (FileNotFoundException e) {
            logger.error(e.getMessage(), e);
        }
        return writeFile(destFilePath, inputStream);
    }

    /**
     * read file to string list, a element of list is a line
     *
     * @param filePath
     * @param charsetName The name of a supported {@link java.nio.charset.Charset <code>charset</code>}
     * @return if file not exist, return null, else return content of file
     * @throws RuntimeException if an error occurs while operator BufferedReader
     */
    public static List<String> readFileToList(String filePath, String charsetName) {
        File file = new File(filePath);
        List<String> fileContent = new ArrayList<>();
        if (!file.isFile()) {
            return fileContent;
        }
        try (InputStreamReader is = new InputStreamReader(new FileInputStream(file), charsetName);
             BufferedReader reader = new BufferedReader(is)) {
            String line;
            while ((line = reader.readLine()) != null) {
                fileContent.add(line);
            }
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
        return fileContent;
    }

    /**
     * get file name from path, not include suffix
     *
     * <pre>
     *      getFileNameWithoutExtension(null)               =   null
     *      getFileNameWithoutExtension("")                 =   ""
     *      getFileNameWithoutExtension("   ")              =   "   "
     *      getFileNameWithoutExtension("abc")              =   "abc"
     *      getFileNameWithoutExtension("a.mp3")            =   "a"
     *      getFileNameWithoutExtension("a.b.rmvb")         =   "a.b"
     *      getFileNameWithoutExtension("c:\\")              =   ""
     *      getFileNameWithoutExtension("c:\\a")             =   "a"
     *      getFileNameWithoutExtension("c:\\a.b")           =   "a"
     *      getFileNameWithoutExtension("c:a.txt\\a")        =   "a"
     *      getFileNameWithoutExtension("/home/admin")      =   "admin"
     *      getFileNameWithoutExtension("/home/admin/a.txt/b.mp3")  =   "b"
     * </pre>
     *
     * @param filePath
     * @return file name from path, not include suffix
     * @see
     */
    public static String getFileNameWithoutExtension(String filePath) {
        if (StringUtils.isEmpty(filePath)) {
            return filePath;
        }

        int extenPosi = filePath.lastIndexOf(FILE_EXTENSION_SEPARATOR);
        int filePosi = filePath.lastIndexOf(File.separator);
        if (filePosi == -1) {
            return (extenPosi == -1 ? filePath : filePath.substring(0, extenPosi));
        }
        if (extenPosi == -1) {
            return filePath.substring(filePosi + 1);
        }
        return (filePosi < extenPosi ? filePath.substring(filePosi + 1, extenPosi)
                : filePath.substring(filePosi + 1));
    }

    /**
     * get file name from path, include suffix
     *
     * <pre>
     *      getFileName(null)               =   null
     *      getFileName("")                 =   ""
     *      getFileName("   ")              =   "   "
     *      getFileName("a.mp3")            =   "a.mp3"
     *      getFileName("a.b.rmvb")         =   "a.b.rmvb"
     *      getFileName("abc")              =   "abc"
     *      getFileName("c:\\")              =   ""
     *      getFileName("c:\\a")             =   "a"
     *      getFileName("c:\\a.b")           =   "a.b"
     *      getFileName("c:a.txt\\a")        =   "a"
     *      getFileName("/home/admin")      =   "admin"
     *      getFileName("/home/admin/a.txt/b.mp3")  =   "b.mp3"
     * </pre>
     *
     * @param filePath
     * @return file name from path, include suffix
     */
    public static String getFileName(String filePath) {
        if (StringUtils.isEmpty(filePath)) {
            return filePath;
        }

        int filePosi = filePath.lastIndexOf(File.separator);
        return (filePosi == -1) ? filePath : filePath.substring(filePosi + 1);
    }

    /**
     * get folder name from path
     *
     * <pre>
     *      getFolderName(null)               =   null
     *      getFolderName("")                 =   ""
     *      getFolderName("   ")              =   ""
     *      getFolderName("a.mp3")            =   ""
     *      getFolderName("a.b.rmvb")         =   ""
     *      getFolderName("abc")              =   ""
     *      getFolderName("c:\\")              =   "c:"
     *      getFolderName("c:\\a")             =   "c:"
     *      getFolderName("c:\\a.b")           =   "c:"
     *      getFolderName("c:a.txt\\a")        =   "c:a.txt"
     *      getFolderName("c:a\\b\\c\\d.txt")    =   "c:a\\b\\c"
     *      getFolderName("/home/admin")      =   "/home"
     *      getFolderName("/home/admin/a.txt/b.mp3")  =   "/home/admin/a.txt"
     * </pre>
     *
     * @param filePath
     * @return
     */
    public static String getFolderName(String filePath) {
        if (StringUtils.isEmpty(filePath)) {
            return filePath;
        }

        int filePosi = filePath.lastIndexOf(File.separator);
        return (filePosi == -1) ? "" : filePath.substring(0, filePosi);
    }

    /**
     * get suffix of file from path
     *
     * <pre>
     *      getFileExtension(null)               =   ""
     *      getFileExtension("")                 =   ""
     *      getFileExtension("   ")              =   "   "
     *      getFileExtension("a.mp3")            =   "mp3"
     *      getFileExtension("a.b.rmvb")         =   "rmvb"
     *      getFileExtension("abc")              =   ""
     *      getFileExtension("c:\\")              =   ""
     *      getFileExtension("c:\\a")             =   ""
     *      getFileExtension("c:\\a.b")           =   "b"
     *      getFileExtension("c:a.txt\\a")        =   ""
     *      getFileExtension("/home/admin")      =   ""
     *      getFileExtension("/home/admin/a.txt/b")  =   ""
     *      getFileExtension("/home/admin/a.txt/b.mp3")  =   "mp3"
     * </pre>
     *
     * @param filePath
     * @return
     */
    public static String getFileExtension(String filePath) {
        if (StringUtils.isBlank(filePath)) {
            return filePath;
        }

        int extenPosi = filePath.lastIndexOf(FILE_EXTENSION_SEPARATOR);
        int filePosi = filePath.lastIndexOf(File.separator);
        if (extenPosi == -1) {
            return "";
        }
        return (filePosi >= extenPosi) ? "" : filePath.substring(extenPosi + 1);
    }

    /**
     * Creates the directory named by the trailing filename of this file, including the complete directory path required
     * to create this directory. <br/>
     * <br/>
     * <ul>
     * <strong>Attentions:</strong>
     * <li>makeDirs("C:\\Users\\Trinea") can only create users folder</li>
     * <li>makeFolder("C:\\Users\\Trinea\\") can create Trinea folder</li>
     * </ul>
     *
     * @param filePath
     * @return true if the necessary directories have been created or the target directory already exists, false one of
     * the directories can not be created.
     * <ul>
     * <li>if {@link FileUtil#getFolderName(String)} return null, return false</li>
     * <li>if target directory already exists, return true</li>
     * <li>return boolean</li>
     * </ul>
     */
    public static boolean makeDirs(String filePath) {
        String folderName = getFolderName(filePath);
        if (StringUtils.isEmpty(folderName)) {
            return false;
        }

        File folder = new File(folderName);
        if (folder.exists() && folder.isDirectory()) {
            return true;
        } else {
            return folder.mkdirs();
        }
    }

    /**
     * @param filePath
     * @return
     * @see #makeDirs(String)
     */
    public static boolean makeFolders(String filePath) {
        return makeDirs(filePath);
    }

    /**
     * Indicates if this file represents a file on the underlying file system.
     *
     * @param filePath
     * @return
     */
    public static boolean isFileExist(String filePath) {
        if (StringUtils.isBlank(filePath)) {
            return false;
        }

        File file = new File(filePath);
        return (file.exists() && file.isFile());
    }

    /**
     * Indicates if this file represents a directory on the underlying file system.
     *
     * @param directoryPath
     * @return
     */
    public static boolean isFolderExist(String directoryPath) {
        if (StringUtils.isBlank(directoryPath)) {
            return false;
        }

        File dire = new File(directoryPath);
        return (dire.exists() && dire.isDirectory());
    }

    /**
     * delete file or directory
     * <ul>
     * <li>if path is null or empty, return true</li>
     * <li>if path not exist, return true</li>
     * <li>if path exist, delete recursion. return true</li>
     * </ul>
     *
     * @param path
     */
    public static void deleteFile(String path) throws IOException {
        if (StringUtils.isBlank(path)) {
            return;
        }
        File file = new File(path);
        if (!file.exists()) {
            return;
        }
        if (file.isFile()) {
            //Files.delete(Paths.get(path));
            if (!file.delete()) {
                logger.error("delete file fail");
            }
            return;
        }
        for (File f : file.listFiles()) {
            if (f.isFile()) {
                try {
                    Path fileName = Paths.get(f.getPath());
                    Files.delete(fileName);
                } catch (Exception ex) {
                    logger.error(ex.getMessage(), ex);
                }
            } else if (f.isDirectory()) {
                deleteFile(f.getAbsolutePath());
            }
        }
        Files.delete(Paths.get(path));
    }

    /**
     * get file size
     * <ul>
     * <li>if path is null or empty, return -1</li>
     * <li>if path exist and it is a file, return file size, else return -1</li>
     * </ul>
     *
     * @param path
     * @return returns the length of this file in bytes. returns -1 if the file does not exist.
     */
    public static long getFileSize(String path) {
        if (StringUtils.isBlank(path)) {
            return -1;
        }

        File file = new File(path);
        return (file.exists() && file.isFile() ? file.length() : -1);
    }

    /**
     * 刪除文件夾
     *
     * @param folderPath
     */
    public static void deleteFolder(String folderPath) {
        try {
            // 删除完里面所有内容
            deleteFile(folderPath);

            //删除目录
            File file = new File(folderPath);
            if (!file.delete()) {
                logger.error("delete file fail");
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }

    /**
     * 创建文件
     *
     * @throws IOException
     */
    public static boolean creatTxtFile(File file) {
        boolean flag = false;
        try {
            if (!file.exists()) {
                if (file.createNewFile()) {
                    flag = true;
                }
            } else {
                flag = true;
            }
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
        return flag;
    }

    /**
     * 写文件
     *
     * @param newStr 新内容
     * @throws IOException
     */
    public static boolean writeTxtFile(File file, String newStr) throws IOException {
        // 先读取原有文件内容，然后进行写入操作
        boolean flag = false;
        String filein = newStr + "\r\n";
        String temp = "";
        StringBuilder buf = new StringBuilder();
        try (FileInputStream fis = new FileInputStream(file);
             InputStreamReader isr = new InputStreamReader(fis);
             BufferedReader br = new BufferedReader(isr)) {
            while ((temp = br.readLine()) != null) {
                buf = buf.append(temp);
                // 行与行之间的分隔符 相当于“\n”
                buf = buf.append(System.getProperty("line.separator"));
            }
            buf.append(filein);
        }
        try (FileOutputStream fos = new FileOutputStream(file);
             PrintWriter pw = new PrintWriter(fos)) {
            pw.write(buf.toString().toCharArray());
            pw.flush();
            flag = true;
        }
        return flag;
    }

    public static char[] loadFile(String file) throws IOException {
        char[] buffer = new char[16 * 1024]; // 16k buffer
        int read;
        try (UnicodeReader r = new UnicodeReader(new FileInputStream(file), null);
             BufferedReader reader = new BufferedReader(r);
             CharArrayWriter writer = new CharArrayWriter();) {

            while ((read = reader.read(buffer)) != -1) {
                writer.write(buffer, 0, read);
            }
            writer.flush();
            return writer.toCharArray();
        }
    }

    /**
     * Save text file as UTF-8 with BOM.
     *
     * @param file   filename
     * @param data   data to be written
     * @param append true, append to existing file. false, overwrite file
     */
    public static void saveFile(String file, String data, boolean append) throws IOException {
        File f = new File(file);
        try (FileOutputStream fos = new FileOutputStream(f, append);
             OutputStreamWriter osw = new OutputStreamWriter(fos, ENCODING);
             BufferedWriter bw = new BufferedWriter(osw)) {
            // write UTF8 BOM mark if file is empty
            if (f.length() < 1) {
                final byte[] bom = new byte[]{(byte) 0xEF, (byte) 0xBB, (byte) 0xBF};
                fos.write(bom);
            }
            if (data != null) {
                bw.write(data);
            }
        }
    }

    /**
     * Load unicode properties file.
     *
     * @param file filename
     */
    public static Map<String, String> loadProperties(String file) throws IOException {
        HashMap<String, String> map = new HashMap<>();
        try (UnicodeReader r = new UnicodeReader(new FileInputStream(file), null);
             BufferedReader reader = new BufferedReader(r)) {
            String line;
            while ((line = reader.readLine()) != null) {
                if (line.equals("")) {
                    continue; // empty line
                }
                int idx = line.indexOf('=');
                if (idx < 1) {
                    continue; // no name=value pair
                }
                String key = line.substring(0, idx).trim();
                if (key.charAt(0) == '#') {
                    continue; // comment line
                }
                String val = line.substring(idx + 1).trim();

                map.put(key, val);
            }
        }
        return map;
    }

    /**
     * Save properties as UTF-8 with BOM file.
     *
     * @param file  filename
     * @param title title row is written to start of file, or null
     * @param props properties
     */
    public static void saveProperties(String file, String title, Map<?, ?> props) throws IOException {
        final char[] newline = System.getProperty("line.separator").toCharArray();
        File f = new File(file);
        try (FileOutputStream fos = new FileOutputStream(f, false);
             OutputStreamWriter osw = new OutputStreamWriter(fos, ENCODING);
             BufferedWriter bw = new BufferedWriter(osw)) {
            // write UTF8 BOM mark
            final byte[] bom = new byte[]{(byte) 0xEF, (byte) 0xBB, (byte) 0xBF};
            fos.write(bom);
            if (title != null) {
                title = "## " + title;
                osw.write(title.toCharArray(), 0, title.length());
                osw.write(newline, 0, newline.length);
            }
            Iterator<?> iter = props.entrySet().iterator();
            while (iter.hasNext()) {
                Map.Entry<?, ?> entry = (Map.Entry<?, ?>) iter.next();
                String key = (String) entry.getKey();
                String val = (String) entry.getValue();
                osw.write(key.toCharArray(), 0, key.length());
                osw.write('=');
                if (val.length() > 0) {
                    osw.write(val.toCharArray(), 0, val.length());
                }
                osw.write(newline, 0, newline.length);
            }
        }
    }

    /**
     * 去掉fileName中的不合法字符\/:*?<>|
     * 同时对html转义符号进行反转义
     *
     * @param fileName
     * @return
     */
    public static String escapeFileName(String fileName) {
        if (StringUtils.isBlank(fileName)) {
            return fileName;
        }

        Pattern pattern = Pattern.compile("[\\s\\\\/:\\*\\?\\\"<>\\|]");
        Matcher matcher = pattern.matcher(fileName);

        fileName = matcher.replaceAll(""); // 将匹配到的非法字符以空替换

        return StringEscapeUtils.unescapeHtml4(fileName);
    }

    /**
     * @param path
     * @return
     * @throws Exception
     */
    public static String getFileMD5(MultipartFile file) throws Exception {
        String asHex = DigestUtils.md5DigestAsHex(file.getInputStream());
        return asHex;
    }

    /**
     * 获取远程文件md5值
     *
     * @param path
     * @return
     */
    public static String getFileMD5(String path) throws Exception {
        URL url = new URL(path);
        HttpURLConnection httpUrlConn = (HttpURLConnection) url.openConnection();
        httpUrlConn.setDoOutput(false);
        httpUrlConn.setDoInput(true);
        httpUrlConn.setUseCaches(false);

        httpUrlConn.setRequestMethod("GET");
        httpUrlConn.connect();

        // 将返回的输入流转换成字符串
        InputStream inputStream = httpUrlConn.getInputStream();
        String md5Hex = DigestUtils.md5DigestAsHex(inputStream);
        return md5Hex;
    }

    /**
     * 发起http请求获取返回结果
     *
     * @param reqUrl 请求地址
     * @return
     */
    public static String httpRequest(String reqUrl) {
        StringBuilder buffer = new StringBuilder();
        try {
            URL url = new URL(reqUrl);
            HttpURLConnection httpUrlConn = (HttpURLConnection) url.openConnection();
            httpUrlConn.setDoOutput(false);
            httpUrlConn.setDoInput(true);
            httpUrlConn.setUseCaches(false);

            httpUrlConn.setRequestMethod("GET");
            httpUrlConn.connect();

            // 将返回的输入流转换成字符串  
            InputStream inputStream = httpUrlConn.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

            String str = null;
            while ((str = bufferedReader.readLine()) != null) {
                buffer.append(str);
            }
            bufferedReader.close();
            inputStreamReader.close();
            // 释放资源  
            inputStream.close();
            httpUrlConn.disconnect();
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return buffer.toString();
    }

    /**
     * 根据路径下载文件
     *
     * @param response
     * @param fileName 文件名称
     * @param filePath 本地文件路径
     */
    public void downloadFile(HttpServletResponse response, String fileName, String filePath) {
        try {
            byte[] byteArray = this.getFileByteArray(filePath);
            // 清空response
            response.reset();
            // 设置response的Header
            response.addHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes()));
            response.addHeader("Content-Length", "" + byteArray.length);
            OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
            response.setContentType("application/octet-stream");
            toClient.write(byteArray);
            toClient.flush();
            toClient.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 下载文件
     *
     * @param filePath
     * @param response
     * @param isOnLine
     * @throws Exception
     */
    public static void downLoad(HttpServletResponse response, String filePath, boolean isOnLine) throws Exception {
        File f = new File(filePath);
        if (!f.exists()) {
            throw new RuntimeException("文件不存在");
        }
        BufferedInputStream br = new BufferedInputStream(new FileInputStream(f));
        byte[] buf = new byte[2048];
        int len = 0;
        //清空response
        response.reset();
        // 在线打开方式
        if (isOnLine) {
            URL u = new URL("file:///" + filePath);
            response.setContentType(u.openConnection().getContentType());
            response.setHeader("Content-Disposition", "inline; filename=" + f.getName());
            // 文件名应该编码成UTF-8
        } else {
            // 纯下载方式
            response.setContentType("application/octet-stream");
            response.setHeader("Content-Disposition", "attachment; filename=" + f.getName());
        }
        OutputStream out = response.getOutputStream();
        while ((len = br.read(buf)) > 0) {
            out.write(buf, 0, len);
        }
        br.close();
        out.close();
    }

    /**
     * 下载网络文件
     *
     * @param url      文件路径
     * @param fileName 文件名称(包含文件后缀)
     * @return void
     */
    public static void downloadByURL(HttpServletResponse response, String remoteFilePath, String fileName) {
        URL urlfile = null;
        HttpURLConnection httpUrl = null;
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            fileName = new String(fileName.getBytes("utf-8"), "iso-8859-1");
            urlfile = new URL(remoteFilePath);
            httpUrl = (HttpURLConnection) urlfile.openConnection();
            httpUrl.connect();
            bis = new BufferedInputStream(httpUrl.getInputStream());
            // 清空response
            response.reset();
            // 设置response的Header
            response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
            response.setContentType("application/octet-stream");
            OutputStream os = response.getOutputStream();
            bos = new BufferedOutputStream(os);
            int len = 2048;
            byte[] b = new byte[len];
            while ((len = bis.read(b)) != -1) {
                bos.write(b, 0, len);
            }
            httpUrl.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                bos.flush();
                bis.close();
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 下载网络文件--通过流
     *
     * @param url      文件路径
     * @param fileName 文件名称(包含文件后缀)
     * @return void
     */
    public static void downLoadByStream(HttpServletResponse response, InputStream inputStream, String fileName) {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            fileName = new String(fileName.getBytes("utf-8"), "iso-8859-1");
            bis = new BufferedInputStream(inputStream);
            // 清空response
            response.reset();
            // 设置response的Header
            response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
            response.setContentType("application/octet-stream");
            OutputStream os = response.getOutputStream();
            bos = new BufferedOutputStream(os);
            int len = 2048;
            byte[] b = new byte[len];
            while ((len = bis.read(b)) != -1) {
                bos.write(b, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                bos.flush();
                bis.close();
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    private static String encodeURIComponent(String value) {
        try {
            return URLEncoder.encode(value, "UTF-8").replaceAll("\\+", "%20");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        }
    }

    //文件类型容器
    public final static Map<String, String> FILE_TYPE_MAP = new HashMap<>(64);

    static {
        getAllFileType();
    }

    /**
     * getAllFileType:存储常见文件头信息. 注意: 以下这些信息可能有所变化, 因为我自己都不知道这些都是什么年头的标签头信息了.
     */
    private static void getAllFileType() {
        FILE_TYPE_MAP.put("ffd8ffe000104a464946", "jpg"); //JPEG (jpg)
        FILE_TYPE_MAP.put("89504e470d0a1a0a0000", "png"); //PNG (png)
        FILE_TYPE_MAP.put("47494638396126026f01", "gif"); //GIF (gif)
        FILE_TYPE_MAP.put("49492a00227105008037", "tif"); //TIFF (tif)
        FILE_TYPE_MAP.put("424d228c010000000000", "bmp"); //16色位图(bmp)
        FILE_TYPE_MAP.put("424d8240090000000000", "bmp"); //24位位图(bmp)
        FILE_TYPE_MAP.put("424d8e1b030000000000", "bmp"); //256色位图(bmp)
        FILE_TYPE_MAP.put("41433130313500000000", "dwg"); //CAD (dwg)
        FILE_TYPE_MAP.put("3c21444f435459504520", "html"); //HTML (html)
        FILE_TYPE_MAP.put("3c21646f637479706520", "htm"); //HTM (htm)
        FILE_TYPE_MAP.put("48544d4c207b0d0a0942", "css"); //css
        FILE_TYPE_MAP.put("696b2e71623d696b2e71", "js"); //js
        FILE_TYPE_MAP.put("7b5c727466315c616e73", "rtf"); //Rich Text Format (rtf)
        FILE_TYPE_MAP.put("38425053000100000000", "psd"); //Photoshop (psd)
        FILE_TYPE_MAP.put("46726f6d3a203d3f6762", "eml"); //Email [Outlook Express 6] (eml)
        FILE_TYPE_MAP.put("d0cf11e0a1b11ae10000", "doc"); //MS Excel 注意：word、msi 和 excel的文件头一样
        FILE_TYPE_MAP.put("d0cf11e0a1b11ae10000", "vsd"); //Visio 绘图
        FILE_TYPE_MAP.put("5374616E64617264204A", "mdb"); //MS Access (mdb)
        FILE_TYPE_MAP.put("252150532D41646F6265", "ps");
        FILE_TYPE_MAP.put("255044462d312e350d0a", "pdf"); //Adobe Acrobat (pdf)
        FILE_TYPE_MAP.put("2e524d46000000120001", "rmvb"); //rmvb/rm相同
        FILE_TYPE_MAP.put("464c5601050000000900", "flv"); //flv与f4v相同
        FILE_TYPE_MAP.put("00000020667479706d70", "mp4");
        FILE_TYPE_MAP.put("49443303000000002176", "mp3");
        FILE_TYPE_MAP.put("000001ba210001000180", "mpg"); //
        FILE_TYPE_MAP.put("3026b2758e66cf11a6d9", "wmv"); //wmv与asf相同
        FILE_TYPE_MAP.put("52494646e27807005741", "wav"); //Wave (wav)
        FILE_TYPE_MAP.put("52494646d07d60074156", "avi");
        FILE_TYPE_MAP.put("4d546864000000060001", "mid"); //MIDI (mid)
        FILE_TYPE_MAP.put("504b0304140000000000", "zip");
        FILE_TYPE_MAP.put("526172211a0700cf9073", "rar");
        FILE_TYPE_MAP.put("235468697320636f6e66", "ini");
        FILE_TYPE_MAP.put("504b03040a0000000000", "jar");
        FILE_TYPE_MAP.put("4d5a9000030000000400", "exe");//可执行文件
        FILE_TYPE_MAP.put("3c25402070616765206c", "jsp");//jsp文件
        FILE_TYPE_MAP.put("4d616e69666573742d56", "mf");//MF文件
        FILE_TYPE_MAP.put("3c3f786d6c2076657273", "xml");//xml文件
        FILE_TYPE_MAP.put("494e5345525420494e54", "sql");//xml文件
        FILE_TYPE_MAP.put("7061636b616765207765", "java");//java文件
        FILE_TYPE_MAP.put("406563686f206f66660d", "bat");//bat文件
        FILE_TYPE_MAP.put("1f8b0800000000000000", "gz");//gz文件
        FILE_TYPE_MAP.put("6c6f67346a2e726f6f74", "properties");//bat文件
        FILE_TYPE_MAP.put("cafebabe0000002e0041", "class");//bat文件
        FILE_TYPE_MAP.put("49545346030000006000", "chm");//bat文件
        FILE_TYPE_MAP.put("04000000010000001300", "mxp");//bat文件
        FILE_TYPE_MAP.put("504b0304140006000800", "docx");//docx文件
        FILE_TYPE_MAP.put("d0cf11e0a1b11ae10000", "wps");//WPS文字wps、表格et、演示dps都是一样的
        FILE_TYPE_MAP.put("6431303a637265617465", "torrent");
        FILE_TYPE_MAP.put("6D6F6F76", "mov"); //Quicktime (mov)
        FILE_TYPE_MAP.put("FF575043", "wpd"); //WordPerfect (wpd)
        FILE_TYPE_MAP.put("CFAD12FEC5FD746F", "dbx"); //Outlook Express (dbx)
        FILE_TYPE_MAP.put("2142444E", "pst"); //Outlook (pst)
        FILE_TYPE_MAP.put("AC9EBD8F", "qdf"); //Quicken (qdf)
        FILE_TYPE_MAP.put("E3828596", "pwl"); //Windows Password (pwl)
        FILE_TYPE_MAP.put("2E7261FD", "ram"); //Real Audio (ram)
    }

    /**
     * 得到上传文件的文件头. 这是个重点!!!!
     * 传入进来的只需要文件输入流的前十个字节!!!
     *
     * @param src
     * @return
     */
    public static String bytesToHexString(byte[] src) {
        StringBuilder stringBuilder = new StringBuilder();
        if (src == null || src.length <= 0) {
            return null;
        }
        //遍历字节数组
        for (int i = 0; i < src.length; i++) {
            //进行位与运算,如果想知道为什么就自己百度,这里就简单的拿来用.
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        //将转换后的十个数字串联在一起就成了一个文件的格式信息(我们看到的是数字,但是这些数字都是有规律的, 你可以每个类型的文件都上传一次,就能看到同类型的文件得到的数字其头部或者尾部是一致的).
        return stringBuilder.toString();
    }


    /**
     * 使用这个工具类时候, 实际上就是调用这个方法即可.
     * 根据指定文件的文件头判断其文件类型---> 上面两个方法都是准备工作,这个才是最好得到文件类型的主方法.
     *
     * @param :MultipartFile multipartFile: 我这里用的是SSM自带的接收上传文件的类. 你可以根据不同的接收进行转变.
     * @return
     */
    public static String getFileType(FileInputStream is) {
        String res = null;
        try {
            //准备数组,固定长度为10,我们只需要前十个.
            byte[] b = new byte[10];
            //读取前十个到数组中去.
            is.read(b, 0, b.length);
            //调用方法,得到该文件的格式数字,说白了, 前面十个字节里存储的就是文件的格式信息,有的在尾部,这个此处不细分,感兴趣的可以百度ID3自己学习.
            String fileCode = bytesToHexString(b);
            //遍历一开始就准备好的格式信息
            Iterator<String> keyIter = FILE_TYPE_MAP.keySet().iterator();
            while (keyIter.hasNext()) {
                String key = keyIter.next();
                //当串联组成的字符串的头或者尾部有等于这个格式信息Map中的数据时候该文件格式就出来了.
                if (key.toLowerCase().startsWith(fileCode.toLowerCase()) || fileCode.toLowerCase().startsWith(key.toLowerCase())) {
                    res = FILE_TYPE_MAP.get(key);
                    break;
                }
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException("文件不存在");
        } catch (IOException e) {
            throw new RuntimeException("获取文件类型失败," + e.getMessage());
        }
        return res;
    }

    /**
     * 获取实际文件类型
     *
     * @param multipartFile
     * @return
     * @throws Exception
     */
    public static String getFileType(MultipartFile multipartFile) {
        try {
            //将接收的上传资源转换成文件输入流.
            FileInputStream is = (FileInputStream) multipartFile.getInputStream();
            String fileType = getFileType(is);
            return fileType;
        } catch (Exception e) {
            throw new RuntimeException("获取文件类型失败," + e.getMessage());
        }
    }

    /**
     * 获取实际文件类型
     *
     * @param multipartFile
     * @return
     * @throws Exception
     */
    public static String getFileOrigType(MultipartFile file) {
        File fileTmp = FileUtil.multi2File(file);
        String result = FileUtil.getType(fileTmp);
        if ("zip".equals(result) && fileTmp.getName().endsWith(".fmu")) {
            result = "fmu";
        }
        return result;
    }

    /**
     * 如果存在同名文件，重命名会返回false
     *
     * @param oldName
     * @param newName
     * @return
     */
    public static boolean renameFile(String oldName, String newName) {
        boolean b = false;
        try {
            File oldFile = new File(oldName);
            File newFile = new File(newName);
            b = oldFile.renameTo(newFile);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return b;
    }

    /**
     * 文件重命名
     *
     * @param pathDir 文件所在路径
     * @param oldName
     * @param newName
     * @return
     */
    public static boolean renameFile(String pathDir, String oldName, String newName) {
        // 文件重命名是否成功
        boolean b = false;
        try {
            File oldFile = new File(pathDir, oldName);
            File newFile = new File(pathDir, newName);
            b = oldFile.renameTo(newFile);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return b;
    }

    /**
     * 将磁盘的多个文件打包成压缩包并输出流下载
     *
     * @param pathList
     * @param request
     * @param response
     */
    public static void zipDirFileToFile(List<Map<String, String>> pathList, HttpServletRequest request, HttpServletResponse response) {
        try {
            // 设置response参数并且获取ServletOutputStream
            ZipArchiveOutputStream zous = getServletOutputStream(response);

            for (Map<String, String> map : pathList) {
                String fileName = map.get("name");
                File file = new File(map.get("path") + fileName);
                InputStream inputStream = new FileInputStream(file);
                setByteArrayOutputStream(fileName, inputStream, zous);
            }
            zous.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void setByteArrayOutputStream(String fileName, InputStream inputStream, ZipArchiveOutputStream zous) throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        while ((len = inputStream.read(buffer)) != -1) {
            baos.write(buffer, 0, len);
        }
        baos.flush();
        byte[] bytes = baos.toByteArray();

        //设置文件名
        ArchiveEntry entry = new ZipArchiveEntry(fileName);
        zous.putArchiveEntry(entry);
        zous.write(bytes);
        zous.closeArchiveEntry();
        baos.close();
    }

    private static ZipArchiveOutputStream getServletOutputStream(HttpServletResponse response) throws Exception {

        String outputFileName = "model-" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".zip";
        response.reset();
        response.setHeader("Content-Type", "application/octet-stream");
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(outputFileName, "UTF-8"));
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Cache-Control", "no-cache");
        ServletOutputStream out = response.getOutputStream();

        ZipArchiveOutputStream zous = new ZipArchiveOutputStream(out);
        zous.setUseZip64(Zip64Mode.AsNeeded);
        return zous;
    }


}
